<?php
/**
 * 功能：定义 Mphp 核心类，并初始化框架基本设置
 * 说明：
 * 创建日期：2012-04-10
 * 更新日期：
 * 作者：Mirze
 * 补充说明： 
 * 
 */
//if(!defined('APP_NAME')) exit("APP_NAME not define!"); //项目模块标识：唯一
//if(!defined('APP_PATH')) exit("APP_PATH not define!"); //项目模块路径

/** Mphp 框架基本库所在路径 **/
define('M_DIR', dirname(__FILE__));

/** DIRECTORY_SEPARATOR 的简写 **/
define('DS', DIRECTORY_SEPARATOR);

/** CURRENT_TIMESTAMP 定义为当前时间，减少框架调用 time() 的次数 **/
define('CURRENT_TIMESTAMP', time());

global $G_CLASS_FILES;
if (empty($G_CLASS_FILES)) {
    require M_DIR . '/_config/mphp_class_files.php';
}

/**
 * (类描述) 类 M 是 MPHP 框架的核心类，提供了框架运行所需的基本服务
 *
 * 基本服务：
 * - 
 * - 
 *
 * @auth
 */
class M
{
	/** 对象注册表 **/
    private static $_objects = array();
    /** 类搜索路径 **/
    private static $_class_path = array();
	/** 类搜索路径的选项 **/
    private static $_class_path_options = array();
	/** 应用程序设置 **/
    private static $_ini = array();
	

	/** 返回 MPHP 版本号 **/
    static function version()
    {
        return '1.0';
    }

	/**
	 * (功能描述) 获取指定的设置内容
     *
     * $option 参数指定要获取的设置名。
     * 如果设置中找不到指定的选项，则返回由 $default 参数指定的值。
     *
     * @code php
     * $option_value = M::ini('my_option');
     * @endcode
     *
     * 对于层次化的设置信息，可以通过在 $option 中使用“/”符号来指定。
     *
     * 例如有一个名为 option_group 的设置项，其中包含三个子项目。
     * 现在要查询其中的 my_option 设置项的内容。
     *
     * @code php
     * // +--- option_group
     * //   +-- my_option  = this is my_option
     * //   +-- my_option2 = this is my_option2
     * //   \-- my_option3 = this is my_option3
     *
     * // 查询 option_group 设置组里面的 my_option 项
     * // 将会显示 this is my_option
     * echo M::ini('option_group/my_option');
     * @endcode
     *
     * 要读取更深层次的设置项，可以使用更多的“/”符号，但太多层次会导致读取速度变慢。
     *
     * 如果要获得所有设置项的内容，将 $option 参数指定为 '/' 即可：
     *
     * @code php
     * // 获取所有设置项的内容
     * $all = M::ini('/');
     * @endcode
     *
     * @param string $option 要获取设置项的名称
     * @param mixed $default 当设置不存在时要返回的设置默认值
     *
     * @return mixed 返回设置项的值
     */
    static function ini($option, $default = null)
    {
        if ($option == '/') return self::$_ini;
        if (strpos($option, '/') === false) {
            return array_key_exists($option, self::$_ini) ? self::$_ini[$option] : $default;
        }

        $parts = explode('/', $option);
        $pos =& self::$_ini;
        foreach ($parts as $part) {
            if (!isset($pos[$part])) return $default;
            $pos =& $pos[$part];
        }
        return $pos;
    }

	/**
	 * (功能描述) 替换已有的设置值 : 直接替换到指定的设置项及其子项目
     *
     * @code php
     * // 假设已有的设置为
     * // +--- option_1 = old value
     * // +--- option_group
     * //   +-- option1 = old value
     * //   +-- option2 = old value
     * //   \-- option3 = old value
     *
     * // 替换多个设置项
     * $arr = array(
     *      'option_1' => 'value 1',
     *      'option_2' => 'value 2',
     *      'option_group/option2' => 'new value',
     * );
     * M::replaceIni($arr);
     *
     * // 修改后
     * // +--- option_1 = value 1
     * // +--- option_2 = value 2
     * // +--- option_group
     * //   +-- option2 = new value
     * @endcode
     *
     * @param string|array $option 要修改的设置项名称，或包含多个设置项目的数组
     * @param mixed $data 指定设置项的新值
     */
	static function replaceIni($option, $data = null)
    {
        if (is_array($option)) {
            self::$_ini = array_merge(self::$_ini, $option);
        } else {
            self::$_ini[$option] = $data;
        }
    }

	/**
	 * (功能描述) 删除指定的设置: 可以删除指定的设置项目及其子项目。
     *
     * @param mixed $option 要删除的设置项名称
     */
    static function cleanIni($option)
    {
        if (strpos($option, '/') === false) {
            unset(self::$_ini[$option]);
        } else {
            $parts = explode('/', $option);
            $max = count($parts) - 1;
            $pos =& self::$_ini;
            for ($i = 0; $i <= $max; $i ++) {
                $part = $parts[$i];
                if ($i < $max) {
                    if (!isset($pos[$part])) {
                        $pos[$part] = array();
                    }
                    $pos =& $pos[$part];
                }  else {
                    unset($pos[$part]);
                }
            }
        }
    }

	/**
	 * (功能描述) 载入指定的文件
     *
     * 该方法从 $dirs 参数提供的目录中查找并载入 $filename 参数指定的文件。
     * 如果文件不存在，则根据 $throw 参数决定是否抛出异常。
     *
     * 与 PHP 内置的 require 和 include 相比，M::loadFile() 会多处下列特征：
     *
     * <ul>
     *   <li>检查文件名是否包含不安全字符；</li>
     *   <li>检查文件是否可读；</li>
     *   <li>文件无法读取时将抛出异常。</li>
     * </ul>
     *
     * @code php
     * M::loadFile('my_file.php', $dirs);
     * @endcode
     *
     * @param string $filename 要载入文件的文件名（含扩展名）
     * @param array $dirs 文件的搜索路径
     * @param boolean $throw 在找不到文件时是否抛出异常
     *
     * @return mixed
     */
    static function loadFile($filename, $dirs = null, $throw = true)
    {
        if (preg_match('/[^a-z0-9\-_.]/i', $filename)) {
            throw new M_ClassException($filename, 2);
        }

        if (is_null($dirs)) {
            $dirs = array();
        } elseif (is_string($dirs)) {
            $dirs = explode(PATH_SEPARATOR, $dirs);
        }
        foreach ($dirs as $dir) {
            $path = rtrim($dir, '\\/') . DS . $filename;
            if (is_file($path)) return include $path;
        }

        if ($throw) throw new M_FileException($filename);
        return false;
    }

	 /**
	 * (功能描述) 返回指定对象的唯一实例
     *
     * M::singleton() 完成下列工作：
     *
     * <ul>
     *   <li>在对象注册表中查找指定类名称的对象实例是否存在；</li>
     *   <li>如果存在，则返回该对象实例；</li>
     *   <li>如果不存在，则载入类定义文件，并构造一个对象实例；</li>
     *   <li>将新构造的对象以类名称作为对象名登记到对象注册表；</li>
     *   <li>返回新构造的对象实例。</li>
     * </ul>
     *
     * 使用 M::singleton() 的好处在于多次使用同一个对象时不需要反复构造对象。
     *
     * @code php
     * // 在位置 A 处使用对象 My_Object
     * $obj = M::singleton('My_Object');
     * ...
     * ...
     * // 在位置 B 处使用对象 My_Object
     * $obj2 = M::singleton('My_Object');
     * // $obj 和 $obj2 都是指向同一个对象实例，避免了多次构造，提高了性能
     * @endcode
     *
     * @param string $class_name 要获取的对象的类名字
     *
     * @return object 返回对象实例
     */
    static function singleton($class_name)
    {
        $key = strtolower($class_name);
        if (isset(self::$_objects[$key]))
        {
            return self::$_objects[$key];
        }
        self::loadClass($class_name);
        return self::register(new $class_name(), $class_name);
    }

	/**
	 * (功能描述) 以特定名字在对象注册表中登记一个对象
     *
     * 开发者可以将一个对象登记到对象注册表中，以便在应用程序其他位置使用 M::registry() 来查询该对象。
     * 登记时，如果没有为对象指定一个名字，则以对象的类名称作为登记名。
     *
     * @code php
     * // 注册一个对象
     * M::register(new MyObject());
     * .....
     * // 稍后取出对象
     * $obj = M::regitry('MyObject');
     * @endcode
     *
     * 当 $persistent 参数为 true 时，对象将被放入持久存储区。
     * 在下一次执行脚本时，可以通过 M::registry() 取出放入持久存储区的对象，并且无需重新构造对象。
     *
     * 利用这个特性，开发者可以将一些需要大量构造时间的对象放入持久存储区，
     * 从而避免每一次执行脚本都去做对象构造操作。
     *
     * 使用哪一种持久化存储区来保存对象，由设置 object_persistent_provier 决定。
     * 该设置指定一个提供持久化服务的对象名。
     *
     * @code php
     * if (!M::isRegistered('MyObject')) {
     *      M::registry(new MyObject(), 'MyObject', true);
     * }
     * $app = M::registry('MyObject');
     * @endcode
     *
     * @param object $obj 要登记的对象
     * @param string $name 用什么名字登记
     * @param boolean $persistent 是否将对象放入持久化存储区
     *
     * @return object
     */
    static function register($obj, $name = null, $persistent = false)
    {
        if (!is_object($obj)) {
            // LC_MSG: Type mismatch. $obj expected is object, actual is "%s".
            throw new M_OtherException($obj);
        }

        // TODO: 实现对 $persistent 参数的支持
        if (is_null($name)) {
            $name = get_class($obj);
        }
        $name = strtolower($name);
        self::$_objects[$name] = $obj;
        return $obj;
    }

    /**
	 * (功能描述) 查找指定名字的对象实例，如果指定名字的对象不存在则抛出异常
     *
     * @code php
     * // 注册一个对象
     * M::register(new MyObject(), 'obj1');
     * .....
     * // 稍后取出对象
     * $obj = M::regitry('obj1');
     * @endcode
     *
     * @param string $name 要查找对象的名字
     * @return object 查找到的对象
     */
    static function registry($name)
    {
        $name = strtolower($name);
        if (isset(self::$_objects[$name])) {
            return self::$_objects[$name];
        }
        // LC_MSG: No object is registered of name "%s".
		throw new M_OtherException($name, 1);
    }

    /**
     * 检查指定名字的对象是否已经注册
     *
     * @param string $name 要检查的对象名字
     *
     * @return boolean 对象是否已经登记
     */
    static function isRegistered($name)
    {
        $name = strtolower($name);
        return isset(self::$_objects[$name]);
    }



	/**
	 * (功能描述) 添加一个类搜索路径
     *
     * 如果要使用 M::loadClass() 载入非 QeePHP 的类，需要通过 M::import() 添加类类搜索路径。
     *
     * 要注意，M::import() 添加的路径和类名称有关系。
     * 例如类的名称为 Vendor_Smarty_Adapter，那么该类的定义文件存储结构就是 vendor/smarty/adapter.php。
     * 因此在用 M::import() 添加 Vendor_Smarty_Adapter 类的搜索路径时，
     * 只能添加 vendor/smarty/adapter.php 的父目录。
     *
     * @code php
     * M::import('/www/app');
     * M::loadClass('Vendor_Smarty_Adapter');
     * // 实际载入的文件是 /www/app/vendor/smarty/adapter.php
     * @endcode
     *
     * 由于 Mphp 的规范是文件名全小写，因此要载入文件名存在大小写区分的第三方库时，
     * 应该指定 import() 方法的第二个参数。
     *
     * @code php
     * M::import('/www/app/vendor');
     * M::loadClass('Zend_Mail');
     * // 实际载入的文件是 /www/app/vendor/Zend/Mail.php
     * @endcode
     *
     * @param string $dir 要添加的搜索路径
     * @param boolean $case_sensitive 在该路径中查找类文件时是否区分文件名大小写
     */
    static function import($dir, $case_sensitive = false)
    {
        $real_dir = realpath($dir);
        if ($real_dir) {
            $dir = rtrim($real_dir, '/\\');
            if (!isset(self::$_class_path[$dir])) {
                self::$_class_path[$dir] = $dir;
                self::$_class_path_options[$dir] = $case_sensitive;
            }
        }
    }

	/**
	 * (功能描述) 用于 Mphp 的类自动载入，不需要由开发者调用
     *
     * @param string $class_name
     */
    static function autoload($class_name)
    {
        self::loadClass($class_name, null, false);
    }

	/**
	 * (功能描述)  载入指定类的定义文件，如果载入失败抛出异常
	 *
	 * @code php
     * Q::loadClass('Table_Posts', array('/www/mysite/app', '/www/mysite/lib'));
     * @endcode
	 *
     * @param string $class_name 要载入的类
     * @param string|array $dirs 指定载入类的搜索路径
     *
     * @return string|boolean 成功返回类名，失败返回 false
	 */
	static function loadClass($class_name, $dirs = null, $throw = true)
    {
		if (class_exists($class_name, false) || interface_exists($class_name, false)) {
            return $class_name;
        }

        global $G_CLASS_FILES;
        $class_name_l = strtolower($class_name);
		if (isset($G_CLASS_FILES[$class_name_l])) {
            require M_DIR . DS . $G_CLASS_FILES[$class_name_l];
            return $class_name_l;
        }

        $filename = str_replace('_', DS, $class_name);
		if ($filename != $class_name) {
            $dirname = dirname($filename);
            if (!empty($dirs)) {
	            if (!is_array($dirs)) {
                    $dirs = explode(PATH_SEPARATOR, $dirs);
                }
            } else {
                $dirs = self::$_class_path;
            }
            $filename = basename($filename) . '.php';
            return self::loadClassFile($filename, $dirs, $class_name, $dirname, $throw);
        } else {
            return self::loadClassFile("{$filename}.php", self::$_class_path, $class_name, '', $throw);
        }
	}

	/**
	 * (功能描述)  载入特定文件，并检查是否包含指定类的定义
     *
     * 该方法从 $dirs 参数提供的目录中查找并载入 $filename 参数指定的文件。
     * 然后检查该文件是否定义了 $class_name 参数指定的类。
     *
     * 如果没有找到指定类，则抛出异常。
     *
     * @code php
     * M::loadClassFile('Smarty.class.php', $dirs, 'Smarty');
     * @endcode
     *
     * @param string $filename 要载入文件的文件名（含扩展名）
     * @param string|array $dirs 文件的搜索路径
     * @param string $class_name 要检查的类
     * @param string $dirname 是否在查找文件时添加目录前缀
     * @param string $throw 是否在找不到类时抛出异常
	 *
     */
	static function loadClassFile($filename, $dirs, $class_name, $dirname = '', $throw = true)
    {
        if (!is_array($dirs)) {
            $dirs = explode(PATH_SEPARATOR, $dirs);
        }
        if ($dirname) {
            $filename = rtrim($dirname, '/\\') . DS . $filename;
        }
        $filename_l = strtolower($filename);

        foreach ($dirs as $dir) {
            if (isset(self::$_class_path[$dir])) {
                $path = $dir . DS . (self::$_class_path_options[$dir] ? $filename : $filename_l);
            } else {
                $path = rtrim($dir, '/\\') . DS . $filename;
            }

            if (is_file($path)) {
                require $path;
                break;
            }
        }

        // 载入文件后判断指定的类或接口是否已经定义
        if (!class_exists($class_name, false) && ! interface_exists($class_name, false)) {
            if ($throw) {
                throw new M_ClassException($class_name, $path);
            }
            return false;
        }
        return $class_name;
    }

	/**
	 * (功能描述) 注册或取消注册一个自动类载入方法
     *
     * 该方法参考 Zend Framework。
     *
     * @param string $class 提供自动载入服务的类
     * @param boolean $enabled 启用或禁用该服务
     */
    static function registerAutoload($class = 'M', $enabled = true)
    {
        if (!function_exists('spl_autoload_register'))
        {
            require_once M_DIR . '/mexception.php';
            throw new MException('spl_autoload does not exist in this PHP installation');
        }

        if ($enabled === true) {
            spl_autoload_register(array($class, 'autoload'));
        } else {
            spl_autoload_unregister(array($class, 'autoload'));
        }
    }

}

/**
 * (功能描述) 内部使用的多语言翻译函数: 应用程序应该使用 MTranslate 组件实现多语言界面。
 *
 * @return $msg
 */
function __()
{
    $args = func_get_args();
    $msg = array_shift($args);
    $language = strtolower(M::ini('error_language'));
    $messages = M::loadFile('lc_messages.php', M_DIR . '/_lang/' . $language, false);
    if (isset($messages[$msg])) {
        $msg = $messages[$msg];
    }
    array_unshift($args, $msg);
    return call_user_func_array('sprintf', $args);
}

/** 设置对象的自动载入 **/
M::registerAutoload();

/** 导入框架函数 **/
include_once(M_DIR . '/core/function.php');

/** 启动引导 **/
Bootstrap::run($app_config);
?>